/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

#include "tempomap.h"
#include <Utils.h>
#include <QJsonObject>

#define __devloglevel__ 4

bool indexCompare(const tempoindex &i1, const tempoindex &i2) {
    return i1.pos < i2.pos;
}
void TempoMap::updateIndexList() {
    qSort(_index.begin(), _index.end(), indexCompare);
    // calculate bar offsets for gui and sound
}

TempoMap::TempoMap() {
}

void TempoMap::addTempo(int pos, float tempo) {
    beginInsertRows(QModelIndex(), 1, 1);
    _tempo[pos] = tempo;
    tempoindex idx;
    idx.pos = pos;
    idx.type = 0;
    _index.append(idx);
    updateIndexList();
    endInsertRows();
}

void TempoMap::addTimeSignature(int pos, int numerator, int denominator) {
    beginInsertRows(QModelIndex(), 1, 1);
    fraction time;
    time.denominator = denominator;
    time.numerator = numerator;
    tempoindex idx;
    idx.pos = pos;
    idx.type = 1;
    _index.append(idx);
    _time[pos] = time;
    updateIndexList();
    endInsertRows();
}

void TempoMap::removeEventAt(int index) {
    if (index < _index.size() && index >= 0) {
        beginRemoveRows(QModelIndex(), index, index);
        if (_index[index].type == 1) {
            _time.remove(_index[index].pos);
        } else if (_index[index].type == 0) {
            _tempo.remove(_index[index].pos);
        }
        _index.removeAt(index);
        updateIndexList();
        endRemoveRows();
    }
}

void TempoMap::removeTempoForBar(int pos)
{
    for(int index=0; index<_index.size(); index++)
    {
        if(_index[index].pos==pos && _index[index].type == 0)
        {
            removeEventAt(index);
            return;
        }
    }
}
void TempoMap::removeTimeSignatureForBar(int pos)
{
    for(int index=0; index<_index.size(); index++)
    {
        if(_index[index].pos==pos && _index[index].type == 1)
        {
            removeEventAt(index);
            return;
        }
    }
}

fraction TempoMap::getTimeSignatureForBar(int pos) {
    fraction time = _time[pos];
    while (time.denominator == 0 && time.numerator == 0) {
        pos--;
        time = _time[pos];
    }
    return time;
}

QModelIndex TempoMap::index(int row, int column,
                            const QModelIndex &parent) const {
    Q_UNUSED(parent);
    return createIndex(row, column);
}

QModelIndex TempoMap::parent(const QModelIndex &child) const {
    Q_UNUSED(child);
    return QModelIndex();
}

int TempoMap::rowCount(const QModelIndex &parent) const {
    Q_UNUSED(parent);
    return _index.count();
}

int TempoMap::columnCount(const QModelIndex &parent) const {
    Q_UNUSED(parent);
    return 3;
}

QVariant TempoMap::data(const QModelIndex &index, int role) const {
    if (role != Qt::DisplayRole) return QVariant();
    int row = index.row();
    int col = index.column();
    int pos = _index[row].pos;
    int type = _index[row].type;
    if (col == 0) return QVariant(pos + 1).toString();
    if (col == 1) {
        if (type == 0) return "Tempo";
        if (type == 1) return "Time Signature";
    }
    if (col == 2) {
        if (type == 0) return _tempo[pos];
        if (type == 1)
            return QVariant(_time[pos].numerator).toString() + "/" +
                   QVariant(_time[pos].denominator).toString();
    }
    return QVariant();
}

void TempoMap::beginEditing() {
    _origIndex = _index;
    _origTempo = _tempo;
    _origTime = _time;
}

void TempoMap::toJson(QJsonArray &array) {
    DEVLOG_DEBUG("tempoMap::toJson "+STR(_index.size()));
    for (int i = 0; i < _index.length(); i++) {
        int pos = _index[i].pos;
        int type = _index[i].type;
        if (type == 1) {
            QJsonObject val;
            val["pos"] = pos;
            val["type"] = "timeSig";
            val["denominator"] = _time[pos].denominator;
            val["numerator"] = _time[pos].numerator;
            DEVLOG_DEBUG("type==1");
            array.append(val);

        } else if (type == 0) {
            QJsonObject val;
            val["pos"] = pos;
            val["type"] = "tempo";
            val["tempo"] = _tempo[pos];
            DEVLOG_DEBUG("type==0");
            array.append(val);
        }
        else {
            DEVLOG_DEBUG("type is invalid");
        }
    }
}

void TempoMap::fromJson(QJsonArray &array) {
    _index.clear();
    _time.clear();
    _tempo.clear();
    for (int i = 0; i < array.size(); i++) {
        QJsonObject val = array[i].toObject();
        if (val["type"].toString() == "timeSig") {
            int pos = val["pos"].toInt();
            int denominator = val["denominator"].toInt();
            int numerator = val["numerator"].toInt();
            this->addTimeSignature(pos, numerator, denominator);
        } else if (val["type"].toString() == "tempo") {
            int pos = val["pos"].toInt();
            int tempo = val["tempo"].toInt();
            this->addTempo(pos, tempo);
        }
    }
}

void TempoMap::undo() {
    _index = _origIndex;
    _tempo = _origTempo;
    _time = _origTime;
}

bool TempoMap::getBar(int pulses, float &time, int &bar, int end) {
    time = 0;
    for (int i = 0; i < 128; i++) {
        fraction ts = getTimeSignatureForBar(i);
        float bpm = getBPMforBar(i);

        int barLength = ts.numerator * 480 * 4 / ts.denominator;
        if (pulses >= barLength) {
            pulses -= barLength;
            time += ts.numerator * 60 / bpm;
        } else {
            pulses += end;
            time += pulses * 60 / bpm / 480;
            bar = i;
            return true;
        }
    }
    return false;
}

float TempoMap::getBPMforBar(int pos) { 
    bool found = _tempo.keys().contains(pos);
    if(found) return _tempo[pos];

    while (pos>0) {
        pos--;

        found = _tempo.keys().contains(pos);
        if(found) return _tempo[pos];
    }

    return 120.0;
}

QString TempoMap::getLabel(int bar, tmlabel mode) {
    if (mode == TM_BPM) {
        if (_tempo.keys().contains(bar) && _tempo[bar] > 0)
            return STR(_tempo[bar]) + " BPM";
    }
    if (mode == TM_SIG) {
        if (_time.keys().contains(bar) && _time[bar].denominator > 0)
            return STR(_time[bar].numerator) + "/" + STR(_time[bar].denominator);
    }

    return "";
}

bool TempoMap::setLabel(tmlabel mode, int bar, QString label) {
    if (mode == TM_BPM) {
        if (label.length() == 0) {
            this->removeTempoForBar(bar);
            return true;
        } else {
            float tempo = QVariant(label).toFloat();
            if (tempo > 0) {
                this->removeTempoForBar(bar);
                this->addTempo(bar, tempo);
                return true;
            }
        }
    }
    if (mode == TM_SIG) {
        if (label.length() == 0) {
            this->removeTimeSignatureForBar(bar);
            return true;
        } else {
            QStringList tmp = label.split("/");
            if (tmp.length() == 2) {
                fraction time;
                time.numerator = QVariant(tmp[0]).toInt();
                time.denominator = QVariant(tmp[1]).toInt();
                if (time.denominator > 0 && time.numerator > 0) {
                    this->removeTimeSignatureForBar(bar);
                    this->addTimeSignature(bar,time.numerator,time.denominator);
                    return true;
                }
            }
        }
    }

    return false;
}
